#include <fstream>
#include <string>

#include "maps.h"

#include "character.h"
#include "coordinates.h"
#include "items.h"
#include "mapController.h"
#include "messagecontroller.h"

using namespace fortress;

map::map()
{
	readItems();
}

map::map(mapController * setMapController):
	width(80), 
	height(24)
{
	gameMaps = setMapController;
	drawDataPtr = new std::vector<std::vector<tile> >(width, std::vector<tile>(height));
	mapData & map = *drawDataPtr;
	for (int i=0; i<width; i++)
	{
		for (int j=0; j<height; j++)
		{
			map[i][j].glyph=' ';
			map[i][j].explored=0;
		}
	}
	monsterList = new std::list<character*>;
	levelMap = new TCODMap(width,height);
	itemMap = new item_map_t;
	portals = new std::vector<portal*>;
	readItems();
	generateMap();
	portals->at(0)->toPortal = 0;
}

map::map(portal * fromPortal, mapController * setMapController):
	width(80), 
	height(24)
{
	gameMaps = setMapController;
	drawDataPtr = new std::vector<std::vector<tile> >(width, std::vector<tile>(height));
	mapData & map = *drawDataPtr;
	for (int i=0; i<width; i++)
	{
		for (int j=0; j<height; j++)
		{
			map[i][j].glyph=' ';
			map[i][j].explored=0;
		}
	}
	monsterList = new std::list<character*>;
	levelMap = new TCODMap(width,height);
	itemMap = new item_map_t;
	portals = new std::vector<portal*>;
	readItems();
	generateMap();
	portals->at(0)->toPortal = fromPortal;
}

map::~map()
{
	delete levelMap;
	// Delete the character list the map holds
	for(std::list<character*>::iterator it=monsterList->begin();it!=monsterList->end();it++)
	{
		delete *it;
	}
	monsterList->clear();
	delete monsterList;
	
	// Delete all items data the map holds
	for(std::map<int, items*>::iterator it = itemData->begin();it!=itemData->end();it++)
	{
		delete it->second;
	}
	itemData->clear();
	delete itemData;
	
	// Delete the items the map holds
	for(item_map_t::iterator it = itemMap->begin(); it!= itemMap->end(); it++)
	{
		while (!it->second->empty())
		{
			items* temp = *it->second->begin();
			it->second->pop_front();
			delete temp;
		}
		delete it->second;
	}
	itemMap->clear();
	delete itemMap;

	// Delete all the portals the map links to
	for (unsigned int i = 0; i<portals->size(); i++)
	{
		delete portals->at(i);
	}
	portals->clear();
	delete portals;
}

void map::load()
{
	for(std::list<character*>::iterator it=monsterList->begin();it!=monsterList->end();it++)
	{
		(*it)->setMap(this);
	}
}

void map::putCharacter(character *getChar)
{
	monsterList->push_back(getChar);
	getChar->setMap(this);
}

void map::releaseCharacter(fortress::character *dropChar)
{
	monsterList->remove(dropChar);
}

void map::killCharacter(character * killChar)
{
	int a,b;
	killChar->getCoords(a,b);
	eventStructure deathEvent(killChar,killChar,event_death,0,a,b);
	fireEventOnTile(deathEvent);
	hearse.push_back(killChar);
}

void map::endTurn()
{
	for (std::list<character*>::iterator it = hearse.begin(); it != hearse.end(); it++)
	{
		releaseCharacter(*it);
		delete *it;
	}
	hearse.clear();
}

void map::readItems()
{
	itemFactory makeItems;
	itemData = makeItems.readFile("levelitems.txt");
}

bool map::travelPortal(int & x, int & y, int type, map *& fromMap)
{
	for(unsigned int i=0; i<portals->size(); i++)
	{
		if (portals->at(i)->x==x && portals->at(i)->y==y && portals->at(i)->type==type)
		{
			if (portals->at(i)->toPortal==0)
			{
				portals->at(i)->toPortal=gameMaps->generateMap(portals->at(i));
				
			}
			x=portals->at(i)->toPortal->x;
			y=portals->at(i)->toPortal->y;
			fromMap=portals->at(i)->toPortal->thisMap;
			return 1;
		}
	}
	return 0;
}

std::list<COORD*> * map::getDrawList(bool redraw)
{
	std::list<COORD*> * tempList = new std::list<COORD*>;
	// check the redraw flag in the future.  right now, we'll always redraw everything
	COORD* temp;

	// make this into a templated function when I'm not so tired

	if (itemMap)
	{
		for(item_map_t::iterator it=itemMap->begin(); it!=itemMap->end(); it++)
		{
			items * data;
			temp = new COORD;
			data = it->second->front();
			data->getCoords(temp->x,temp->y);
			temp->glyph = data->getGlyph();
			temp->color = data->getColor();
			tempList->push_back(temp);
		}
	}
	if (monsterList)
	{
		for(std::list<character*>::iterator it=monsterList->begin(); it!=monsterList->end(); it++)
		{
			character * data;
			temp = new COORD;
			data = *it;
			data->getCoords(temp->x,temp->y);
			temp->glyph = data->getGlyph();
			temp->color = data->getColor();
			tempList->push_back(temp);
		}
	}

	return tempList;	// be sure to take care of this list's memory
}

void map::loadFile(char * filename)
{
// The upstairs MUST, MUST, MUST, MUST, MUST be the first portal.
	std::string *line = new std::string;
	std::ifstream FILE;
	FILE.open(filename);
	if (FILE.is_open())
	{
		int i=0;
		while (!FILE.eof())
		{
			std::getline(FILE, *line);
			if (line->length()==width && i<height)
			{
				for(int j=0; j<width; j++)
				{
					mapData & drawData = *drawDataPtr;
					drawData[j][i].glyph=line->c_str()[j];
					if(line->c_str()[j]=='+' || line->c_str()[j]=='>' || line->c_str()[j]=='<')
					{
						std::map<int, items*>::iterator it;
						if (line->c_str()[j]=='+')
							it = itemData->find(2);
						// The upstairs MUST, MUST, MUST, MUST, MUST be the first portal.
						else if (line->c_str()[j]=='<')
							it = itemData->find(0);
						else if (line->c_str()[j]=='>')
							it = itemData->find(1);
						items * tempCopy = it->second;
						items * temp = new items(*tempCopy);
						temp->setCoords(j,i);
						temp->setGlyph(temp->getGlyph());
						temp->setColor(temp->getColor());

						dropItem(*temp);

						// The drawn data does not include items
						drawData[j][i].glyph='.';

					}
				}
			}
			i++;

		}
	}
	FILE.close();
	delete line;
}

int map::convertXYList(int x, int y) const
{
	return (x*height)+y;
}

void map::mergeItemList(std::list<items*> &newItems)
{
	for(std::list<items*>::iterator it=newItems.begin(); it != newItems.end(); it++)
	{
		dropItem(*(*it));
	}
}

void map::dropItem(items & item)
{
	item_map_t::iterator it2;
	int x,y,z;
	item.getCoords(x,y);
	if (x<0 || x > width || y<0 || y > height)
	{
		throw (std::string("Item found outside of expected bounds!"));
	}
	z=convertXYList(x,y);
	it2 = itemMap->find(z);
	if (it2 == itemMap->end())
	{
		itemMap->insert(item_map_pair_t(z,new std::list<items*>));
		it2 = itemMap->find(z);
	} 
	// Add in the item
	std::list<items*> * buriedList;
	buriedList = it2->second;
	buriedList->push_back(&item);
}

char map::getTile(int x, int y) const
{
	mapData & drawData = *drawDataPtr;
	return drawData[x][y].glyph;
}

bool map::getExplored(int x, int y) const
{
	mapData & drawData = *drawDataPtr;
	if (levelMap->isInFov(x,y))
		drawData[x][y].explored=1;
	return drawData[x][y].explored;
}

bool map::getBlocked(int x, int y) const
{
	items *tempItem;
	item_map_t::iterator tempIt = itemMap->find(convertXYList(x,y));
	if (tempIt != itemMap->end())
	{
		std::list<items*>* list = tempIt->second;
		//std::list<items*>* list = itemMap[convertXYList(x,y)];
		for (std::list<items*>::iterator it = list->begin(); it != list->end(); it++)
		{
			tempItem = *it;
			if (!tempItem->getWalkable())
				return 1;
		}
	}
	return 0;
}

eventReceiver * map::getCharacter(int x, int y) const
{
	for (std::list<character*>::iterator it = monsterList->begin(); it != monsterList->end(); it++)
	{
		int a,b;
		(*it)->getCoords(a,b);
		if (x==a && y==b)
			return *it;
	}
	return 0;
}

void map::getUpstair(int &getX, int &getY)
{
	items *tempItem;
	for (item_map_t::iterator it = itemMap->begin(); it != itemMap->end(); it++)
	{
		for (std::list<items*>::iterator it2 = it->second->begin(); it2 != it->second->end(); it2++)
		{
			tempItem = *it2;
			if (tempItem->getGlyph()=='<')
			{
				tempItem->getCoords(getX, getY);
				return;
			}
		}
	}
	throw std::string("Upstairs not found.");
}

portal * map::getUpstairPortal()
{
	return portals->at(0);
}

bool map::fireEventOnTile(eventStructure const & getEvent)
{
	items *tempItem;
	item_map_t::iterator tempIt = itemMap->find(convertXYList(getEvent.x,getEvent.y));
	if (tempIt != itemMap->end())
	{
		std::list<items*>* list = tempIt->second;
		for (std::list<items*>::iterator it = list->begin(); it != list->end(); it++)
		{
			tempItem = *it;
			tempItem->exec((int)getEvent.type,0);
			tempItem->doEvent(getEvent);
			if (tempItem->getName().compare(std::string("doorClosed"))==0)
			{
				if (getEvent.type==event_open)
				{
					getItemList(getEvent.x,getEvent.y)->front()->convertItem(*itemData->find(3)->second);
					return 1;
				}
			}
		}
	}

	character * toChar=0;
	// calculate LOS, then, send it to whatever characters can see it

	for(std::list<character*>::iterator it = monsterList->begin(); it != monsterList->end(); it++)
	{
		character * tempChar = *it;
		int a,b;
		tempChar->getCoords(a,b);
		if (getEvent.x == a && getEvent.y == b)
		{
			toChar=tempChar;
		}
	}
	if (!toChar)
		return 0;

		calculateVisibility(getEvent.x,getEvent.y);
	for(std::list<character*>::iterator it = monsterList->begin(); it != monsterList->end(); it++)
	{
		character * tempChar = *it;
		int a,b;
		tempChar->getCoords(a,b);
		if (getEvent.x != a || getEvent.y != b)
		{
			tempChar->sense(getEvent, *toChar, levelMap->isInFov(a,b), 0);
		}
	}

	if (toChar)
	{
		if (toChar!=getEvent.originator)	// Prevent an event from triggering itself
		{
			toChar->doEvent(getEvent);
		}
	}
	
	return 0;
}

std::list<items*> const * map::getItemList(int x, int y) const
{
	if (itemMap->find(convertXYList(x,y))==itemMap->end())
		return 0;
	return (itemMap->find(convertXYList(x,y))->second);
}

bool map::doesItemStackExist(int x, int y) const
{
	if (itemMap->find(convertXYList(x,y))==itemMap->end())
		return 0;
	return 1;
}

items & map::pickupItem(int x,int y,items const * item)
{
	if (itemMap->find(convertXYList(x,y))!=itemMap->end())
	{
		std::list<items*> *tempList = (itemMap->find(convertXYList(x,y))->second);
		
		for (std::list<items*>::iterator it = tempList->begin(); it != tempList->end(); it++)
		{
			items * tempItem = *it;
			if (tempItem == item)
			{
				tempList->erase(it);
				clearListIfEmpty(convertXYList(x,y));
				return *tempItem;
			}
		}
	}
	throw (std::string("Expected item not found by function pickupItem!"));
}

void map::clearListIfEmpty(int num)
{
	if (itemMap->find(num)!=itemMap->end())
	{
		std::list<items*> *tempList = (itemMap->find(num))->second;
		if (tempList->empty())
		{
			itemMap->erase(num);
			delete tempList;
		}
	}
}

void map::generateMap()
{
	mapData & drawData = *drawDataPtr;
	TCODRandom * rng = TCODRandom::getInstance();
	bool valid=0;
	int minWidth=4;
	int maxWidth=15;
	int maxHeight=9;
	
	roomVec allRooms;
	// Try to build random maps
	tryToGenerateAgain:
	int tries=0;
	int numRooms=rng->getGaussianInt(3,10);

	for (int i=0; i<numRooms; i++)
	{
		valid=1;
		buildRoom currentRoom;
/*		currentRoom.height = rng->getGaussianInt(minWidth, maxHeight);
		currentRoom.width = rng->getGaussianInt(minWidth, maxWidth);
*/
		currentRoom.height = rng->getInt(minWidth, maxHeight);
		currentRoom.width = rng->getInt(minWidth, maxWidth);
		currentRoom.x = rng->getInt(1, (width-2)-currentRoom.width);
		currentRoom.y = rng->getGaussianInt(2, (height-1)-currentRoom.height);

		double doorRng = rng->getFloat(0,1);
		int numDoors;	// Number of doors in this room
		if (doorRng < .1)		// 10% chance of 4 doors
			numDoors=4;			// 20% chance of 3 doors
		else if (doorRng < .3)	// 40% chance of 2 doors
			numDoors=3;			// 30% chance of 1 door
		else if (doorRng < .7)
			numDoors=2;
		else
			numDoors=1;
		for (int j=0; j<numDoors; j++)
		{
			int side=rng->getInt(0,3);
			int disX=rng->getInt(1,currentRoom.width-2);
			int disY=rng->getInt(1,currentRoom.height-2);
			newDoor thisDoor;
			thisDoor.x=currentRoom.x+disX;
			thisDoor.y=currentRoom.y+disY;
			thisDoor.side=side;
			switch (side)
			{
			case 0:	// Top
				thisDoor.y=currentRoom.y;
				break;
			case 1: // Right
				thisDoor.x=currentRoom.x+currentRoom.width-1;
				break;
			case 2: // Bottom
				thisDoor.y=currentRoom.y+currentRoom.height-1;
				break;
			case 3: // Left
				thisDoor.x=currentRoom.x;
				break;
			}

			bool adjacentDoor=0;
			for (int k=0;k<j;k++)
			{
				int x0=currentRoom.doors[k].x;
				int y0=currentRoom.doors[k].y;
				int x1=thisDoor.x;
				int y1=thisDoor.y;
				if ((x0==x1 && (y0-y1) >= -1 && (y0-y1) <= 1) || (y0==y1 && (x0-x1) >= -1 && (x0-x1) <= 1))// Doors are adjacent
					adjacentDoor=1;
			}
			if (!adjacentDoor)
				currentRoom.doors.push_back(thisDoor);
			else
				j--;
		}

		for (int j=0; j<i; j++)
		{
			if (intersects(currentRoom, allRooms[j]))
			{
				valid=0;
				break;
			}
		}
		if (valid)
		{
			allRooms.push_back(currentRoom);
		}
		else
		{
			tries++;
			i--;
			if (tries==500)
			{
				goto tryToGenerateAgain;
			}
		}
	}

	// Draw level data for the generated rooms
	for (int h=0; h<numRooms; h++)
	{
		for (int i=allRooms[h].x; i<(allRooms[h].x+allRooms[h].width); i++)
		{
			for (int j=allRooms[h].y; j<(allRooms[h].y+allRooms[h].height); j++)
			{
				if (j==allRooms[h].y || j==(allRooms[h].y+allRooms[h].height)-1)
					drawData[i][j].glyph='-';
				else if (i==allRooms[h].x || i==(allRooms[h].x+allRooms[h].width)-1)
					drawData[i][j].glyph='|';
				else
				{
					drawData[i][j].glyph='.';
				}
			}
		}

		// create door objects
		for (unsigned int i=0; i<allRooms[h].doors.size(); i++)	// Place the doors
		{
			// make sure that there is no door on this spot already
			if (getItemList(allRooms[h].doors[i].x,allRooms[h].doors[i].y)==0)
			{
				int door=2;
				items * temp = new items(*itemData->find(door)->second);
				temp->setCoords(allRooms[h].doors[i].x,allRooms[h].doors[i].y);
				drawData[allRooms[h].doors[i].x][allRooms[h].doors[i].y].glyph='.';
				dropItem(*temp);
			}
		}
	}
	// draw primary hallways network
	TCODPath hallway = TCODPath::TCODPath(width, height, new hallCallback, &drawData, 0);
	for (int h=0; h<numRooms; h++)
	{	
		// draw out the hallways from this room to the next room
		int z=h+1;
		if (z==numRooms)
			z=0;
		messageController & messages = messageController::getInstance();
		int x=allRooms[h].doors[0].x;
		int y=allRooms[h].doors[0].y;
		buildDoorwaySetReset(x,y,allRooms[h].doors[0].side,1);

		if (!hallway.compute(x,y,allRooms[z].doors[0].x,allRooms[z].doors[0].y))
			messages.error("Could not find a path for a primary hallway!");
		for (int i=0; i < hallway.size(); i++ ) 
		{
			int hallX,hallY;
			hallway.get(i,&hallX,&hallY);
			if (drawData[hallX][hallY].glyph!='.')
				drawData[hallX][hallY].glyph='#';
		}
		buildDoorwaySetReset(x,y,0,0);
	}
	// draw secondary hallways
	for (int h=0; h<numRooms; h++)
	{
		for (unsigned int i=1; i<allRooms[h].doors.size(); i++)
		{
			int x=allRooms[h].doors[i].x;
			int y=allRooms[h].doors[i].y;
			buildDoorwaySetReset(x,y,allRooms[h].doors[i].side,1);

			int to=h;
			while (to==h)
			{
				to=rng->getInt(0,numRooms-1);
			}
			int toDoor=rng->getInt(0,allRooms[to].doors.size()-1);
			messageController & messages = messageController::getInstance();
			messages.debug("Drawing from room ").debug(h).debug(" at (").debug(allRooms[h].x).debug(",").debug(allRooms[h].y).debug(") ").debug(" door ").debug(i).debug(" to room" ).debug(to).debug(" door ").debug(toDoor).debug("\n");
			if (!hallway.compute(x,y,allRooms[to].doors[toDoor].x,allRooms[to].doors[toDoor].y))
				messages.error("Could not draw hallway!");
			for (int i=0; i < hallway.size(); i++ ) 
			{
				int hallX,hallY;
				hallway.get(i,&hallX,&hallY);
				if (drawData[hallX][hallY].glyph!='.')
					drawData[hallX][hallY].glyph='#';
			}
			// Fill in the start and end of these paths, since they don't actually start in the doorway
			hallway.getOrigin(&x,&y);
			drawData[x][y].glyph='#';
			hallway.getDestination(&x,&y);
			drawData[x][y].glyph='#';
			int a;
			buildDoorwaySetReset(a,a,0,0);
		}
	}
	
	// generate an upstairs
	items * temp = new items(*itemData->find(0)->second);
	int x=0;
	int y=0;
	int terribleInfiniteLoopPreventerThisIsTheWorst=0;
	// this could cause an infinite loop if rooms are not generated properly
	while (drawData[x][y].glyph!='.' || getItemList(x,y)!=0)
	{
		x=rng->getInt(2,width-2);
		y=rng->getInt(2,height-2);
		terribleInfiniteLoopPreventerThisIsTheWorst++;
		if (terribleInfiniteLoopPreventerThisIsTheWorst==2000)
			throw std::string("Error creating the dungeon, no rooms found!");
	}
	temp->setCoords(x,y);
	dropItem(*temp);
	portal * tempPortal = new portal;
	tempPortal->x=x;
	tempPortal->y=y;
	tempPortal->thisMap=this;
	tempPortal->toPortal=0;
	tempPortal->type=1;
	portals->push_back(tempPortal);

	// generate a downstairs
	temp = new items(*itemData->find(1)->second);
	x=0;
	y=0;
	while (drawData[x][y].glyph!='.' || getItemList(x,y)!=0)
	{
		x=rng->getInt(2,width-2);
		y=rng->getInt(2,height-2);
	}
	temp->setCoords(x,y);
	dropItem(*temp);
	tempPortal = new portal;
	tempPortal->x=x;
	tempPortal->y=y;
	tempPortal->thisMap=this;
	tempPortal->toPortal=0;
	tempPortal->type=0;
	portals->push_back(tempPortal);
	for (int i=0; i < width; i++)
	{
		for (int j=0; j<height; j++)
		{
			if (drawData[i][j].glyph=='#' || drawData[i][j].glyph=='.')
				levelMap->setProperties(i,j,1,1);	// Set the square to be transparent and walkable
		}
	}
	for (int h=0; h<numRooms; h++)
	{
		for (unsigned int i=1; i<allRooms[h].doors.size(); i++)
		{
			int x=allRooms[h].doors[i].x;
			int y=allRooms[h].doors[i].y;
			levelMap->setProperties(x,y,0,0);
		}
	}
}

bool map::intersects(buildRoom const & A, buildRoom const & B)
{
	bool xInt=0;
	bool yInt=0;
	if (!(A.x > (B.x+B.width) || (A.x+A.width) < B.x))
		xInt=1;
	if (!(A.y > (B.y+B.height) || (A.y+A.height) < B.y))
		yInt=1;
	return (xInt && yInt);
}

float hallCallback::getWalkCost(int xFrom, int yFrom, int xTo, int yTo, void *userData) const
{
	std::vector<std::vector<tile> > * data;
	data = reinterpret_cast<std::vector<std::vector<tile> > *>(userData);
	char tile = ((*data)[xTo][yTo].glyph);
	if (tile == '.') return 1.0f;
	if (tile == '#') return 0.01f;
	if (tile == ' ') return 1.0f;
	else return 0;
}

void map::buildDoorwaySetReset(int & x, int & y, int side, bool set)
{
	mapData & drawData = *drawDataPtr;
	static int saveX, saveY;
	if (set)
	{
		saveX=x;
		saveY=y;
		drawData[saveX][saveY].glyph='X';
		if (side==0)
			y--;
		else if (side==1)
			x++;
		else if (side==2)
			y++;
		else if (side==3)
			x--;
		else
			throw std::string("Invalid input to buildDoorwaySetReset()");
	}
	else
	{
		drawData[saveX][saveY].glyph='.';
	}
}

void map::calculateVisibility(int x, int y)
{
	levelMap->computeFov(x,y,0,1,FOV_BASIC);
}